/*
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * PropertyHandler.java
 * Copyright (C) 2004-2012 University of Waikato, Hamilton, New Zealand
 */

package weka.core.xml;

import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;

/**
 * This class stores information about properties to ignore or properties that
 * are allowed for a certain class.
 * 
 * @author FracPete (fracpete at waikato dot ac dot nz)
 * @version $Revision$
 */
public class PropertyHandler {

    /**
     * contains display names of properties to ignore in the serialization process
     * 
     * @see #ignored()
     * @see #addIgnored(String)
     * @see #removeIgnored(String)
     * @see #isIgnored(String)
     */
    protected Hashtable<Object, HashSet<String>> m_Ignored = null;

    /**
     * lists for a class the properties allowed to use for setting and getting. if a
     * class is not listed, then all get/set-methods are allowed.<br>
     * Mapping: classname (String) - properties (HashSet, containing the
     * Property-Names)
     * 
     * @see #allowed()
     * @see #addAllowed(Class,String)
     * @see #removeAllowed(Class,String)
     * @see #isAllowed(Class,String)
     */
    protected Hashtable<Object, HashSet<String>> m_Allowed = null;

    /**
     * initializes the handling
     */
    public PropertyHandler() {
        super();

        m_Ignored = new Hashtable<Object, HashSet<String>>();
        m_Allowed = new Hashtable<Object, HashSet<String>>();
    }

    /**
     * returns an enumeration of the stored display names and classes of properties
     * to ignore.<br>
     * <b>NOTE:</b> String and Class Objects are mixed in this enumeration,
     * depending whether it is a global property to ignore or just one for a certain
     * class!
     * 
     * @return the display names and classes
     * @see #m_Ignored
     */
    public Enumeration<Object> ignored() {
        return m_Ignored.keys();
    }

    /**
     * adds the given display name of a property to the ignore list. Can either be a
     * complete path (e.g. <code>__root__.options</code>) or only a property name
     * (e.g. <code>options</code>). In the latter case it matches all occurences of
     * this display name.
     * 
     * @param displayName the property to ignore
     * @see #m_Ignored
     */
    public void addIgnored(String displayName) {
        HashSet<String> list;

        list = new HashSet<String>();
        list.add(displayName);

        m_Ignored.put(displayName, list);
    }

    /**
     * adds the given class with the display name of a property to the ignore list.
     * I.e. this property is only ignored for this class.
     * 
     * @param c           the class for which a property is to be ignored
     * @param displayName the property to ignore
     * @see #m_Ignored
     */
    public void addIgnored(Class<?> c, String displayName) {
        HashSet<String> list;

        // retrieve list
        if (m_Ignored.containsKey(c)) {
            list = m_Ignored.get(c);
        } else {
            list = new HashSet<String>();
            m_Ignored.put(c, list);
        }

        list.add(displayName);
    }

    /**
     * removes the given display name from the ignore list. returns whether the
     * removing was succesful, i.e. whether the display name was in the list.
     * 
     * @param displayName the property to remove from the ignore list
     * @return whether the ignore list contained the specified property
     * @see #m_Ignored
     */
    public boolean removeIgnored(String displayName) {
        return (m_Ignored.remove(displayName) != null);
    }

    /**
     * removes the given display name from the ignore list of the class. returns
     * whether the removing was succesful, i.e. whether the display name was in the
     * list.
     * 
     * @param c           the class to remove the property from
     * @param displayName the property to remove from the ignore list
     * @return whether the ignore list contained the specified property
     * @see #m_Ignored
     */
    public boolean removeIgnored(Class<?> c, String displayName) {
        HashSet<String> list;

        // retrieve list
        if (m_Ignored.containsKey(c)) {
            list = m_Ignored.get(c);
        } else {
            list = new HashSet<String>();
        }

        return list.remove(displayName);
    }

    /**
     * checks whether the given display name is an ignored property
     * 
     * @param displayName the property to check whether it is on the ignore list
     * @return whether the property is in the ignored list
     * @see #m_Ignored
     */
    public boolean isIgnored(String displayName) {
        return m_Ignored.containsKey(displayName);
    }

    /**
     * checks whether the given display name of a certain class is an ignored
     * property. It only checks for this certain class and no derivative classes. If
     * you also want to check for derivative classes, use
     * <code>isIgnored(Object,String)</code>.
     * 
     * @param c           the class to check the property for
     * @param displayName the property to check whether it is on the ignore list
     * @return whether the property is in the ignored list
     * @see #m_Ignored
     * @see #isIgnored(Object, String)
     */
    public boolean isIgnored(Class<?> c, String displayName) {
        HashSet<String> list;

        // retrieve list
        if (m_Ignored.containsKey(c)) {
            list = m_Ignored.get(c);
        } else {
            list = new HashSet<String>();
        }

        return list.contains(displayName);
    }

    /**
     * checks whether the given display name of a given object is an ignored
     * property. The object is checked for each stored class whether it is an
     * <code>instanceof</code>. If the class is not stored then it will default to
     * <code>false</code>, since there are no restrictions for this class.
     * 
     * @param o           the object to check the property for
     * @param displayName the property to check whether it is on the ignore list
     * @return whether the property is in the ignored list
     * @see #m_Ignored
     */
    public boolean isIgnored(Object o, String displayName) {
        Enumeration<Object> enm;
        Class<?> c;
        Object element;
        boolean result;
        HashSet<String> list;

        result = false;

        enm = ignored();
        while (enm.hasMoreElements()) {
            element = enm.nextElement();

            // has to be class! not a display name
            if (!(element instanceof Class)) {
                continue;
            }

            c = (Class<?>) element;

            // is it an instance of this class?
            if (c.isInstance(o)) {
                list = m_Ignored.get(c);
                result = list.contains(displayName);
                break;
            }
        }

        return result;
    }

    /**
     * returns an enumeration of the classnames for which only certain properties
     * (display names) are allowed
     * 
     * @return the classnames with restriction to properties
     */
    public Enumeration<Object> allowed() {
        return m_Allowed.keys();
    }

    /**
     * adds the given property (display name) to the list of allowed properties for
     * the specified class.
     * 
     * @param c           the class to add a property for
     * @param displayName the property to allow for the class
     * @see #m_Allowed
     */
    public void addAllowed(Class<?> c, String displayName) {
        HashSet<String> list;

        // retrieve list
        list = m_Allowed.get(c);
        if (list == null) {
            list = new HashSet<String>();
            m_Allowed.put(c, list);
        }

        // add property
        list.add(displayName);
    }

    /**
     * removes the given property (display name) for the specified class from the
     * list of allowed properties.
     * 
     * @param c           the class to remove the property for
     * @param displayName the property to remove
     * @return whether the property was found
     * @see #m_Allowed
     */
    public boolean removeAllowed(Class<?> c, String displayName) {
        boolean result;
        HashSet<String> list;

        result = false;

        // retrieve list
        list = m_Allowed.get(c);

        // remove property
        if (list != null) {
            result = list.remove(displayName);
        }

        return result;
    }

    /**
     * returns whether the given property (display name) is allowed for the given
     * class. It only checks for this certain class and no derivative classes. If
     * you also want to check for derivative classes, use
     * <code>isAllowed(Object,String)</code>.
     * 
     * @param c           the class to check the property for
     * @param displayName the property (display name) to check
     * @return whether the property is allowed in that context
     * @see #m_Allowed
     * @see #isAllowed(Object, String)
     */
    public boolean isAllowed(Class<?> c, String displayName) {
        boolean result;
        HashSet<String> list;

        result = true;

        // retrieve list
        list = m_Allowed.get(c);

        // check list
        if (list != null) {
            result = list.contains(displayName);
        }

        return result;
    }

    /**
     * returns whether the given property (display name) is allowed for the given
     * object . The object is checked for each stored class whether it is an
     * <code>instanceof</code>. If the class is not stored then it will default to
     * <code>true</code>, since there are no restrictions for this class.
     * 
     * @param o           the object to check the property for
     * @param displayName the property (display name) to check
     * @return whether the property is allowed in that context
     */
    public boolean isAllowed(Object o, String displayName) {
        Enumeration<Object> enm;
        Class<?> c;
        boolean result;
        HashSet<String> list;

        result = true;

        enm = allowed();
        while (enm.hasMoreElements()) {
            c = (Class<?>) enm.nextElement();

            // is it an instance of this class?
            if (c.isInstance(o)) {
                list = m_Allowed.get(c);
                result = list.contains(displayName);
                break;
            }
        }

        return result;
    }

}
